#include "OpenglAppFramework/inc/PlugIn/Camera.h"
#include "OpenglAppFramework/inc/Shader.h"
#include "OpenglAppFramework/inc/GameObject.h"
#include "OpenglAppFramework/inc/Scene.h"
#include "OpenglAppFramework/inc/SceneConfig.h"
#include "OpenglAppFramework/inc/Utils.h"
#include "OpenglAppFramework/inc/glm/glm.hpp"
#include "OpenglAppFramework/inc/glm/gtc/matrix_transform.hpp"
#include "OpenglAppFramework/inc/glm/gtc/type_ptr.hpp"
#include <iostream>

// glm::vec3 Camera::m_front_vec;
// Camera::MouseInfo Camera::m_mouse_info;

#define DFT_CAM_SPEED   (0.08f)
Camera::Camera(const std::string& name):
    PlugIn(name, PlugInType::Camera) {
    m_up_vec = glm::vec3(0.0f, 1.0f, 0.0f);
    m_front_vec = glm::vec3(0.0f, 0.0f, -1.0f);
    m_proj_mat = glm::mat4(0.0f);
    m_lookat_mat = glm::mat4(0.0f);
    m_cam_speed = DFT_CAM_SPEED;
    m_proj_type = ProjectionType::Perspective;
    m_up = m_down = m_left = m_right = 0.0f;
    m_enable_asdw = m_enable_mouse = false;
}

Camera::~Camera() {}

int Camera::setConfigData(const nlohmann::json& data) {
    const std::string& proj_type = data["projection_type"];
    if(proj_type == "perspective") {
        m_proj_type = ProjectionType::Perspective;
        m_fovy = data["fovy_degree"].get<float>();
        m_aspect = data["aspect"].get<float>();
    }
    else if(proj_type == "orth") {
        m_proj_type = ProjectionType::Orth;
        auto& tmp = data["orth_left_right_down_up"];
        m_left = tmp[0].get<float>();
        m_right = tmp[1].get<float>();
        m_down = tmp[2].get<float>();
        m_up = tmp[3].get<float>();
    }
    else {
        std::cout << "illegal projection type: " << proj_type << std::endl;
        return -1;
    }
    m_near = data["near"].get<float>();
    m_far = data["far"].get<float>();
    json2Vec3(data["front_vec"], m_front_vec);
    json2Vec3(data["up_vec"], m_up_vec);
    if(data.find("enable_asdw") != data.end()) {
        m_enable_asdw = data["enable_asdw"].get<bool>();
    }
    if(data.find("enable_mouse") != data.end()) {
        m_enable_mouse = data["enable_mouse"].get<bool>();
    }

    return 0;
}

void Camera::uploadShaderData(Shader* shader) {
    shader->setMat4("proj_mat", m_proj_mat);
    shader->setMat4("view_mat", m_lookat_mat);
    shader->setVec3("cam_location", m_obj->transformInfo().location);
    // std::cout << "proj" << std::endl << mat2str(m_proj_mat) << std::endl
    //     << "view" << std::endl << mat2str(m_lookat_mat) << std::endl
    //     << "loc" << std::endl << vec2str(m_obj->transformInfo().location) << std::endl;
}

#define FAKE_FLOAT_ZERO   (0.0000001)
int Camera::init() {
    const glm::vec3& location = m_obj->transformInfo().location;

    if(m_proj_type == ProjectionType::Perspective) {
        m_proj_mat = glm::perspective(glm::radians(m_fovy), m_aspect, m_near, m_far);
    }
    else {
        m_proj_mat = glm::ortho(m_left, m_right, m_down, m_up, m_near, m_far);
    }
    m_up_vec = glm::normalize(m_up_vec);
    m_front_vec = glm::normalize(m_front_vec);

    // 根据front_vec初始化yaw/pitch
    float abs_x = glm::abs(m_front_vec.x); if(abs_x <= FAKE_FLOAT_ZERO) {abs_x = FAKE_FLOAT_ZERO;}
    float abs_y = glm::abs(m_front_vec.y); if(abs_y <= FAKE_FLOAT_ZERO) {abs_y = FAKE_FLOAT_ZERO;}
    float abs_z = glm::abs(m_front_vec.z); if(abs_z <= FAKE_FLOAT_ZERO) {abs_z = FAKE_FLOAT_ZERO;}
    float tmp;
    // yaw: 在xz平面上，front_vec与-z轴的夹角（度），向右为正
    tmp = glm::degrees(glm::atan(abs_x / abs_z));
    if(m_front_vec.x < 0) {
        tmp = -tmp;
    }
    m_mouse_info.yaw = tmp;
    // pitch: front_vec与xz平面的夹角（度），y轴方向为正
    tmp = glm::sqrt(m_front_vec.x * m_front_vec.x + m_front_vec.z * m_front_vec.z);
    if(tmp >= 0 && tmp < FAKE_FLOAT_ZERO) {tmp = FAKE_FLOAT_ZERO;}
    else if(tmp < 0 && tmp > -FAKE_FLOAT_ZERO) {tmp = -FAKE_FLOAT_ZERO;}
    m_mouse_info.pitch = glm::degrees(glm::acos(tmp));
    if(m_front_vec.y < 0) {
        m_mouse_info.pitch = - m_mouse_info.pitch;
    }

    const glm::vec3& cam_location = m_obj->transformInfo().location;
    m_lookat_mat = glm::lookAt(cam_location, cam_location + m_front_vec, m_up_vec);

    std::cout <<"[init] " <<  m_name << " cam_pos: " << vec2str(m_obj->transformInfo().location) << " m_front:" << vec2str(m_front_vec) 
        << " m_up: " << vec2str(m_up_vec)
        << " yaw:" << m_mouse_info.yaw << " pitch:" << m_mouse_info.pitch 
        << std::endl;

    return 0;
}

void Camera::destroy() {}

void Camera::routine() {
    OpenglEnv& env = m_obj->scene()->env();
    glm::vec3 cam_location = m_obj->transformInfo().location;
    // 处理键盘输入，只修改cam_location
    if(m_enable_asdw == true) {
        if (glfwGetKey(env.window, GLFW_KEY_W) == GLFW_PRESS) {
            cam_location += m_cam_speed * m_front_vec;
        }
        if (glfwGetKey(env.window, GLFW_KEY_S) == GLFW_PRESS) {
            cam_location -= m_cam_speed * m_front_vec;
        }
        if (glfwGetKey(env.window, GLFW_KEY_A) == GLFW_PRESS) {
            cam_location -= glm::normalize(glm::cross(m_front_vec, m_up_vec)) * m_cam_speed;
        }
        if (glfwGetKey(env.window, GLFW_KEY_D) == GLFW_PRESS) {
            cam_location += glm::normalize(glm::cross(m_front_vec, m_up_vec)) * m_cam_speed;
        }
        if (env.key_q) {
            cam_location += m_cam_speed * glm::vec3(0, 1, 0);
        }
        if (env.key_e) {
            cam_location += m_cam_speed * glm::vec3(0, -1, 0);
        }
        m_obj->setLocation(cam_location);
    }

    // 处理鼠标输入，只修改front_vec
    if(m_enable_mouse) {
        glfwGetCursorPos(env.window, &m_mouse_info.cur_x, &m_mouse_info.cur_y);
        if(m_mouse_info.is_first) {
            m_mouse_info.last_x = m_mouse_info.cur_x;
            m_mouse_info.last_y = m_mouse_info.cur_y;
            m_mouse_info.is_first = false;
        }
        double delta_x = m_mouse_info.cur_x - m_mouse_info.last_x;
        double delta_y = m_mouse_info.last_y - m_mouse_info.cur_y; // reversed since y-coordinates go from bottom to top
        m_mouse_info.last_x = m_mouse_info.cur_x;
        m_mouse_info.last_y = m_mouse_info.cur_y;
        delta_x *= m_mouse_info.sensitivity;
        delta_y *= m_mouse_info.sensitivity;
        m_mouse_info.yaw += delta_x;
        m_mouse_info.pitch += delta_y;
        if (m_mouse_info.pitch > 89.0f) {
            m_mouse_info.pitch = 89.0f;
        }
        if (m_mouse_info.pitch < -89.0f) {
            m_mouse_info.pitch = -89.0f;
        }
        glm::vec3 front;

        // front.x = sin(glm::radians(m_mouse_info.yaw)) * cos(glm::radians(m_mouse_info.pitch));
        // front.y = sin(glm::radians(m_mouse_info.pitch));
        // front.z = -cos(glm::radians(m_mouse_info.yaw)) * cos(glm::radians(m_mouse_info.pitch));

        // front.x = sin(glm::radians(m_mouse_info.yaw));
        // front.z = -cos(glm::radians(m_mouse_info.yaw));
        // front.y = tan(glm::radians(m_mouse_info.pitch)) * sqrt(front.x * front.x + front.z * front.z);

        front.x = sin(glm::radians(m_mouse_info.yaw)) * cos(glm::radians(m_mouse_info.pitch));
        front.z = -cos(glm::radians(m_mouse_info.yaw)) * cos(glm::radians(m_mouse_info.pitch));
        front.y = tan(glm::radians(m_mouse_info.pitch)) * sqrt(front.x * front.x + front.z * front.z);
        m_front_vec = glm::normalize(front);

        // std::cout << m_name << " cam_pos: " << vec2str(m_obj->transformInfo().location) 
        //     << " front:" << vec2str(front) << " m_front:" << vec2str(m_front_vec) 
        //     << " yaw:" << m_mouse_info.yaw << " pitch:" << m_mouse_info.pitch 
        //     << " delta_xy: " << delta_x << ' ' << delta_y
        //     << std::endl;
    }
    m_lookat_mat = glm::lookAt(cam_location, cam_location + m_front_vec, m_up_vec);
    // m_lookat_mat = glm::lookAt(cam_location, m_front_vec, m_up_vec);
}

const glm::mat4& Camera::getProjMat() const {
    return m_proj_mat;
}

const glm::mat4& Camera::getViewMat() const {
    return m_lookat_mat;
}

void Camera::setFrontVec(const glm::vec3& front_vec) {
    m_front_vec = front_vec;
}
